<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>二维码矫正演示</title>
  <script src="./plugins/jsfeat-min.js"></script>
  <script src="./plugins/jsQR.js"></script>
  <style>
    canvas {
      border: 1px solid #ccc;
      margin: 10px;
    }

    #container {
      display: flex;
      gap: 20px;
    }

    #container div {
      text-align: center;
    }
  </style>
</head>

<body>

  <h2>二维码图像矫正</h2>
  <input type="file" id="fileInput" accept="image/*" />

  <div id="container">
    <div>
      <p>原始图像</p>
      <canvas id="originalCanvas"></canvas>
    </div>
    <div>
      <p>矫正后的二维码</p>
      <canvas id="correctedCanvas"></canvas>
    </div>
  </div>

  <script>
    const fileInput = document.getElementById('fileInput');
    const originalCanvas = document.getElementById('originalCanvas');
    const correctedCanvas = document.getElementById('correctedCanvas');
    const ctxOriginal = originalCanvas.getContext('2d');
    const ctxCorrected = correctedCanvas.getContext('2d');

    fileInput.addEventListener('change', function (e) {
      const file = e.target.files[0];
      if (!file) return;

      const img = new Image();
      const reader = new FileReader();

      reader.onload = function (evt) {
        img.onload = function () {
          originalCanvas.width = img.width;
          originalCanvas.height = img.height;
          ctxOriginal.drawImage(img, 0, 0);

          const imageData = ctxOriginal.getImageData(0, 0, img.width, img.height);
          processImage(imageData);
        };
        img.src = evt.target.result;
      };

      reader.readAsDataURL(file);
    });

    function laplacian (gray, width, height) {
      // 5. 否则，纯 JS + Canvas2D 实现
      let sum = 0, sum2 = 0, N = width * height;
      for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
          let L = 0;
          if (x > 0 && x < width - 1 && y > 0 && y < height - 1) {
            L = -4 * gray[y * width + x]
              + gray[y * width + (x - 1)]
              + gray[y * width + (x + 1)]
              + gray[(y - 1) * width + x]
              + gray[(y + 1) * width + x];
          }
          sum += L;
          sum2 += L * L;
        }
      }
      const mean = sum / N;
      return sum2 / N - mean * mean;
    }

    function processImage (imageData) {
      const { data, width, height } = imageData;


      console.time()
      // 检测二维码
      const qrCode = jsQR(data, width, height);
      if (!qrCode) {
        alert("未检测到二维码！");
        return;
      }

      console.log("识别结果：", qrCode.data);

      const { topLeftCorner, topRightCorner, bottomRightCorner, bottomLeftCorner } = qrCode.location;

      // 计算二维码的平均宽度和高度作为目标尺寸
      const width1 = Math.hypot(topRightCorner.x - topLeftCorner.x, topRightCorner.y - topLeftCorner.y);
      const width2 = Math.hypot(bottomRightCorner.x - bottomLeftCorner.x, bottomRightCorner.y - bottomLeftCorner.y);
      const height1 = Math.hypot(bottomLeftCorner.x - topLeftCorner.x, bottomLeftCorner.y - topLeftCorner.y);
      const height2 = Math.hypot(bottomRightCorner.x - topRightCorner.x, bottomRightCorner.y - topRightCorner.y);

      const dstWidth = Math.round((width1 + width2) / 2);
      const dstHeight = Math.round((height1 + height2) / 2);

      const corners = [
        topLeftCorner,
        topRightCorner,
        bottomRightCorner,
        bottomLeftCorner
      ];

      const size = Math.max(dstWidth, dstHeight);

      const src_pts = corners.map(p => [p.x, p.y]);
      const dst_pts = [
        [0, 0],
        [size, 0],
        [size, size],
        [0, size]
      ];

      console.log('pts', src_pts, dst_pts);


      // 创建变换矩阵
      const warp_mat = new jsfeat.matrix_t(3, 3, jsfeat.F32_t | jsfeat.C1_t);


      // 计算透视变换矩阵
      jsfeat.math.perspective_4point_transform(
        warp_mat,
        dst_pts[0][0], dst_pts[0][1], src_pts[0][0], src_pts[0][1],
        dst_pts[1][0], dst_pts[1][1], src_pts[1][0], src_pts[1][1],
        dst_pts[2][0], dst_pts[2][1], src_pts[2][0], src_pts[2][1],
        dst_pts[3][0], dst_pts[3][1], src_pts[3][0], src_pts[3][1]
      );

      console.log('warp_mat', warp_mat, data);

      //图像数据
      // 使用原始图像尺寸创建矩阵
      let src_mat = new jsfeat.matrix_t(width, height, jsfeat.F32_t | jsfeat.C1_t);

      // 转换为灰度图像（使用原始图像尺寸）
      jsfeat.imgproc.grayscale(data, width, height, src_mat, jsfeat.COLOR_RGBA2GRAY);

      // 创建目标矩阵（使用计算出的尺寸）
      const dst_mat = new jsfeat.matrix_t(size, size, jsfeat.U8_t | jsfeat.C1_t);

      // 检查变换矩阵是否有效
      if (warp_mat.data[8] === 0) {
        console.error("无效的变换矩阵");
        return;
      }


      console.log('mat', src_mat, dst_mat);

      // 执行透视变换（添加边界填充）
      jsfeat.imgproc.warp_perspective(src_mat, dst_mat, warp_mat, jsfeat.BILINEAR_INTERPOLATION || 0);

      // --- 将 U8_t 灰度数据转换为 ImageData ---
      const outImageData = ctxCorrected.createImageData(size, size);
      const outData = outImageData.data; // Uint8ClampedArray
      const srcData = dst_mat.data;      // Uint8Array (因为 dst_mat 是 U8_t)
      const numPixels = size * size;

      for (let i = 0; i < numPixels; i++) {
        const grayValue = srcData[i]; // 值在 0-255 之间
        const dataIndex = i * 4;
        outData[dataIndex] = grayValue; // R
        outData[dataIndex + 1] = grayValue; // G
        outData[dataIndex + 2] = grayValue; // B
        outData[dataIndex + 3] = 255;       // A
      }

      console.log('laplacian', laplacian(dst_mat.data, size, size));

      correctedCanvas.width = size;
      correctedCanvas.height = size;
      ctxCorrected.putImageData(outImageData, 0, 0);

      console.timeEnd()
    }
  </script>

</body>

</html>